package main;
import java.awt.Graphics2D;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;


public class Artificial_Agent {

	private final int VISION_RANGE;
	private boolean[][]mental_grid = new boolean[30][50];//defaults to false
	private ArrayList<Location> checkpoints = new ArrayList<Location>(),
								path = new ArrayList<Location>();
	private Location closeLoc;
	private boolean endLocationSpotted = false; 
	private int stuckChecker = 0;
	private Random rand;
	private Location currentLoc;
	private SearchStatus agent_status = SearchStatus.NOTSEARCHING;
	
	public Artificial_Agent(Location loc, int range)
	{
		currentLoc = loc;
		VISION_RANGE = range;
		agent_status = SearchStatus.SEARCHING;
		rand = new Random();
		for(int row = 5; row < 30; row+=10)
		{
			for(int col = 5; col < 50; col+=10)
			{
				checkpoints.add(Grid.grid[row][col]);
			}
		}
		closeLoc = getGoodLocation();
		analyzeGrid();
	}
	public void draw(Graphics2D g)
	{
		g.setColor(new Color (64, 255, 0));
		g.fillRect(currentLoc.col()*20, currentLoc.row()*20, 20, 20);
		
	}
	public void update() {
		if(agent_status == SearchStatus.SEARCHING)
		{
			if (!endLocationSpotted)
			{
				if (closeLoc == currentLoc)
				{
					closeLoc = getGoodLocation();
				}
				path = Astar(currentLoc, closeLoc);
				while (path == null)
				{
					closeLoc.setToUnreachable();//the intended location cannot be reached and therefore must be marked as such
					if (Grid.allUnreachable())//if all locations have proven to be unreachable then the search has failed
					{
						agent_status = SearchStatus.FAILURE;
						break;
					}
					closeLoc = getGoodLocation();//find another location to reach
					path = Astar(currentLoc, closeLoc);//attempt to establish a path
				}
				if (path != null)
				{
					Collections.reverse(path);
					currentLoc = path.remove(1);
					stuckChecker = 0;
				}
				else
				{
					closeLoc = getGoodLocation();
					stuckChecker++;//if it is unable to move find a path to the location twice
				}				   //in a row then it's most probably stuck in a closed of section
				analyzeGrid();     //and the end location cannot be reached.
			}
			else
			{
				if (currentLoc == closeLoc)
					agent_status = SearchStatus.SUCCESSFUL;
				path = Astar(currentLoc, closeLoc);
				if (path != null && agent_status == SearchStatus.SEARCHING)
				{
					if(path.size() == 2)//for some reason, without this line of code, the program keeps saying
						agent_status = SearchStatus.SUCCESSFUL;//that the end location is unreachable even though
					Collections.reverse(path);				   //the AI was able to reach it.
					currentLoc = path.remove(1);
					analyzeGrid();
				}
				else
					agent_status = SearchStatus.FAILURE;
			}
			//if(stuckChecker > 10) // TODO: It might be better to start marking grid locations as unreachable -Tyler
				//agent_status = SearchStatus.FAILURE;
		}	
	}
	public void analyzeGrid()
	{
		ArrayList<Location> line_of_sight = Grid.getAdjacentLocations(currentLoc, VISION_RANGE);
		for(Location visible_location: line_of_sight)
		{
			mental_grid[visible_location.row()][visible_location.col()] = true;
			if(Grid.isEndPoint(visible_location))
			{
				endLocationSpotted = true;
				closeLoc = visible_location;
			}
		}
	}
	public Location getClosestLocation()
	{
		if(checkpoints.isEmpty())
			return null;
		int closestDistance = Integer.MAX_VALUE;
		ArrayList<Location> closestLocs = new ArrayList<Location>();
		for (Location loc: checkpoints)
		{
			int distance = Math.max(Math.abs(currentLoc.row()-loc.row()),Math.abs(currentLoc.col()-loc.col()));
			if(distance < closestDistance)
			{
				closestLocs.clear();
				closestDistance = distance;
				closestLocs.add(loc);
			}
			else if (distance == closestDistance)
				closestLocs.add(loc);
		}
		int selectedIndex = rand.nextInt(closestLocs.size());
		checkpoints.remove(checkpoints.indexOf(closestLocs.get(selectedIndex)));
		return closestLocs.get(selectedIndex);
	}
	public Location getLocationWithLowestFScore(ArrayList<Location> list)
	{
		Location lowestFScore = list.get(0);
		for(int index = 1; index < list.size(); index++)
			lowestFScore = list.get(index).getFValue() < lowestFScore.getFValue() ? list.get(index) : lowestFScore;
		return lowestFScore;
	}	
	public ArrayList<Location> Astar(Location start,Location goal)
	{
		ArrayList<Location> closedList = new ArrayList<Location>(),
		openList = new ArrayList<Location>();
		openList.add(start);
		start.setParent(null);//sets parent to null and doesn't change g_value (which is initialized to 0)
		start.setHeuristicValue(goal);//calculates h_value based on the locations distance from the goal and also sets the f_value
		
		while(!openList.isEmpty())
		{
			Location current = getLocationWithLowestFScore(openList);
			if(current == goal)
				return getFinalPath(goal);
			openList.remove(current);
			closedList.add(current);
			for(Location childLoc: current.getAdjacentLocations(mental_grid))
			{
				if(closedList.contains(childLoc))
					continue;
				int tenative_g_score = current.getGValue() + 10;
				boolean tenative_is_better;
				if(!openList.contains(childLoc))
				{
					openList.add(childLoc);
					tenative_is_better = true;
				}
				else if (tenative_g_score < childLoc.getGValue())
					tenative_is_better = true;
				else
					tenative_is_better = false;
				if(tenative_is_better)
				{
					childLoc.setParent(current);
					childLoc.setGValue(tenative_g_score);
					childLoc.setHeuristicValue(goal);
				}
			}
		}
		return null; //search has failed to yield any results
	}
	public ArrayList<Location> getFinalPath(Location endLoc)
	{
		ArrayList<Location> finalPath = new ArrayList<Location>();
		finalPath.add(endLoc);
		while(endLoc.getParent() != null)
		{
			endLoc = endLoc.getParent();
			finalPath.add(endLoc);
		}
		return finalPath;
	}
	public Location getGoodLocation()
	{
		closeLoc = getClosestLocation();
		if(closeLoc == null)//if its already been to all the checkpoints
		{
			int row = rand.nextInt(30);
			int column = rand.nextInt(50);
			while(mental_grid[row][column])//moves to areas that it hasn't already seen
			{
				row = rand.nextInt(30);
				column = rand.nextInt(50);
			}
			return Grid.grid[row][column];
		}
		return closeLoc;
	}
	public SearchStatus getSuccess()
	{return agent_status;}
	public int getStuck()
	{return stuckChecker;}
}
